﻿using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.IMGUI.Controls;

//------------------------------------------------------------
// shaco Framework
// Copyright © 2017-2022 chang.liu All rights reserved.
// Feedback: 449612236@qq.com
//------------------------------------------------------------

namespace shaco.Instance.FindReference
{
    public class FindReferenceWindow : EditorWindow
    {
        private enum FindInternalDepnciesType
        {
            Unknown,
            SceneAsset,
            Prefab
        }

        private struct FindInternalDepnciesInfo
        {
            public string path;
            public string propertyPath;
        }

        //被依赖信息
        public class DependentInfo
        {
            public string assetPath { get { return _assetPath; } private set { _assetPath = value; } }
            public List<string> dependencies = new List<string>();

            private string _assetPath = null;

            public DependentInfo(string assetPath)
            {
                this.assetPath = assetPath;
            }
        }

        //动态获取到的引用信息
        public class DepenciesInfo
        {
            public string guid = null;
            public string assetPath = null;
        }

        //缓存起来的引用信息
        public class CacheDepenciesInfo
        {
            public string assetPath { get { return _assetPath; } private set { _assetPath = value; } }
            public Hash128 hash128 { get { return _hash128; } private set { _hash128 = value; } }
            public string[] dependenciesGUID = null;

            private string _assetPath = null;
            private Hash128 _hash128;

            public CacheDepenciesInfo(string assetPath, Hash128 hash128)
            {
                this.hash128 = hash128;
                this.assetPath = assetPath;
            }
        }

        public enum FindStatus
        {
            None,
            NoReference,
            FindReference,
        }

        private shaco.Instance.Editor.TreeView.PathTreeView _treeViewUnuse = null;
        private shaco.Instance.Editor.TreeView.PathTreeView _treeViewReferenced = null;
        private shaco.Instance.Editor.TreeView.WindowSplitter _dragLineSeparator = new shaco.Instance.Editor.TreeView.WindowSplitter(shaco.Instance.Editor.TreeView.WindowSplitter.Direction.Vertical);
        private Rect _prevRectDrawFolder = new Rect();
        private Rect _currentRectDrawFolder = new Rect();
        private FindStatus _statusFind = FindStatus.None;
        private int _iReferenceCountTmp = 0;
        private List<string> _unusedFilePath = new List<string>();
        private Dictionary<TreeViewItem, Dictionary<Object, FindInternalDepnciesInfo>> _showFoundDepenciesItemsPath = new Dictionary<TreeViewItem, Dictionary<Object, FindInternalDepnciesInfo>>();
        private IEnumerable<string> _currentSelectionPathWheSingleClick = null;

        private const float GUI_WIDTH_MENU_BUTTON = 45;
        static private IList _currentForeachCollections = null;
        static private System.Func<object, bool> _currentForeachCallbackData = null;
        static private System.Action<float> _currentForeachcallbackProgress = null;
        static private int _currentForeachCountInPerFrame = 1;
        static private int _currentForeachIndex = 0;

        [MenuItem("Assets/Find References In Project %#&f", false, 20)]
        static void OpenFindReferenceWindowInProjectMenu()
        {
            var selectAssetTmp = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);

            if (selectAssetTmp.Length > 0)
            {
                var assetsPathTmp = new string[selectAssetTmp.Length];
                for (int i = 0; i < selectAssetTmp.Length; ++i)
                {
                    assetsPathTmp[i] = AssetDatabase.GetAssetPath(selectAssetTmp[i]);
                }
                OpenFindReferenceWindow().FindReferencesInProject(assetsPathTmp);
            }
        }

        static FindReferenceWindow OpenFindReferenceWindow()
        {
            var retValue = EditorWindow.GetWindow<FindReferenceWindow>(true, "FindReference");
            retValue.Init();
            return retValue;
        }

        static public void FindReferencesInProject(System.Action<Dictionary<string, DependentInfo>> callbackEnd, bool useProgressBar, params string[] selectAssets)
        {
            FindReferencesInProjectBase(callbackEnd, useProgressBar, selectAssets);
        }

        void Init()
        {
            _treeViewUnuse = new shaco.Instance.Editor.TreeView.PathTreeView();
            _treeViewReferenced = new shaco.Instance.Editor.TreeView.PathTreeView();

            _treeViewUnuse.callbackTopBarGUI += () =>
            {
                GUILayout.Label("Unuse Asset Count:" + _treeViewUnuse.fileCount);
                return 18;
            };

            _treeViewReferenced.callbackTopBarGUI += () =>
            {
                GUILayout.Label("Depencies Asset Count:" + _treeViewReferenced.fileCount);
                return 18;
            };

            _treeViewUnuse.callbackSubTopBarGUI += () =>
            {
                if (0 == _treeViewUnuse.fileCountSub)
                    return 18;

                GUILayout.Label("Child Count:" + _treeViewUnuse.fileCountSub);
                return 18;
            };

            _treeViewReferenced.callbackSubTopBarGUI += () =>
            {
                if (0 == _treeViewReferenced.fileCountSub)
                    return 18;

                var selectionPath = _treeViewReferenced.GetSelectionPath();
                var isSelectedFolder = null != selectionPath && selectionPath.Any(v => System.IO.Directory.Exists(v));
                var showFoundDepenciesItemsPathCount = 0;
                foreach (var iter in _showFoundDepenciesItemsPath)
                    showFoundDepenciesItemsPathCount += iter.Value.Count;
                GUILayout.Label((isSelectedFolder ? "Child Count:" : "Referenced Count:") + (_treeViewReferenced.fileCountSub - showFoundDepenciesItemsPathCount));
                return 18;
            };

            _treeViewReferenced.SetItemMenu("Deep Find Reference", (path, data) =>
            {
                if (null != data)
                    OpenFindReferenceWindow().FindReferencesInProject(data.ToArray());
            }, data => null != data && data.Any(v => null != v.customData));

            _treeViewReferenced.SetItemMenu("Instantiate prefabs in scene", (path, data) =>
            {
                if (null != data)
                {
                    CoroutineInstantiatePrefabInScene(data);
                }
            }, data => null != data && data.Any(v => null != v.customData) && data.Any(v => v.customData.Any(v => v.EndsWith(".prefab"))));

            _treeViewReferenced.AddHeader("ExpandAll", () => _treeViewReferenced.ExpandAll());
            _treeViewReferenced.AddHeader("CollapseAll", () => _treeViewReferenced.CollapseAll());
            _treeViewUnuse.AddHeader("ExpandAll", () => _treeViewUnuse.ExpandAll());
            _treeViewUnuse.AddHeader("CollapseAll", () => _treeViewUnuse.CollapseAll());

            _treeViewReferenced.callbackDrawRightViewItem = OnDrawFindReferenceTarget;
            _treeViewUnuse.callbackDrawRightViewItem = OnDrawOpenReferenceTarget;

            _treeViewReferenced.callbackSingleClickedItem = OnRefereceViewSingleClickedItemCallBack;
        }

        void CoroutineInstantiatePrefabInScene(IList<string> list)
        {
            var index = 0;
            ForeachCoroutine(list.ToArray(), data =>
            {
                var iter = data as string;
                var useCancel = EditorUtility.DisplayCancelableProgressBar("Instantiating prefab...", $"{System.IO.Path.GetFileName(iter)} ({index + 1}/{list.Count})", index / (float)list.Count);
                ++index;
                if (useCancel)
                {
                    EditorUtility.ClearProgressBar();
                    return false;
                }

                if (iter.EndsWith(".prefab"))
                    PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>(iter));
            
                if (index == list.Count)
                    EditorUtility.ClearProgressBar();
                return true;
            }, null, 2f / list.Count);
        }

        void OnGUI()
        {
            if (null == _treeViewUnuse)
                return;

            if (_dragLineSeparator.isDragSplitter)
                this.Repaint();

            if (_statusFind == FindStatus.None)
            {
                GUILayout.Label("please select a asset in ''Project' window\n");
            }

            GUILayout.Space(0);

            var currentEvent = Event.current;
            if (_prevRectDrawFolder != this.position)
            {
                if ((null != currentEvent && currentEvent.type == EventType.Repaint))
                {
                    var lastRect = GUILayoutUtility.GetLastRect();
                    _prevRectDrawFolder = this.position;
                    _currentRectDrawFolder = new Rect(0, lastRect.yMax, this.position.width, this.position.height - lastRect.yMax);

                    //设置拆分绘制窗口
                    if (_treeViewUnuse.hasChildren && _treeViewReferenced.hasChildren)
                    {
                        _dragLineSeparator.SetSplitWindow(_currentRectDrawFolder);
                    }
                }
            }

            //带分割线的绘制
            if (_treeViewUnuse.hasChildren && _treeViewReferenced.hasChildren)
            {
                _dragLineSeparator.BeginLayout(true);
                {
                    _treeViewUnuse.DrawTreeView(_dragLineSeparator.GetSplitWindowRect(0));
                }
                _dragLineSeparator.EndLayout();
                _dragLineSeparator.BeginLayout();
                {
                    _treeViewReferenced.DrawTreeView(_dragLineSeparator.GetSplitWindowRect(1));
                }
                _dragLineSeparator.EndLayout();
            }
            //单独绘制
            else if (_treeViewUnuse.hasChildren)
            {
                _treeViewUnuse.DrawTreeView(_currentRectDrawFolder);
            }
            else if (_treeViewReferenced.hasChildren)
            {
                _treeViewReferenced.DrawTreeView(_currentRectDrawFolder);
            }
        }

        static private void Update()
        {
            if (null == _currentForeachCollections)
            {
                EditorApplication.update -= Update;
                return;
            }

            //停止遍历
            if (_currentForeachIndex >= _currentForeachCollections.Count)
            {
                _currentForeachIndex = 0;
                _currentForeachCollections = null;
                if (null != _currentForeachcallbackProgress)
                    _currentForeachcallbackProgress(1.0f);
                return;
            }

            var loopCount = System.Math.Min(_currentForeachCountInPerFrame, _currentForeachCollections.Count - _currentForeachIndex);
            for (int i = 0; i < loopCount; ++i)
            {
                var index = _currentForeachIndex + i;
                var data = _currentForeachCollections[index];
                if (!_currentForeachCallbackData(data))
                {
                    _currentForeachIndex = 0;
                    _currentForeachCollections = null;
                    if (null != _currentForeachcallbackProgress)
                        _currentForeachcallbackProgress(1.0f);
                    return;
                }
            }

            _currentForeachIndex += loopCount;
            var currentPercent = (float)_currentForeachIndex / (float)_currentForeachCollections.Count;
            if (currentPercent >= 1.0f)
                currentPercent = 0.99f;

            if (null != _currentForeachcallbackProgress)
                _currentForeachcallbackProgress(currentPercent);
        }

        private void OnDrawOpenReferenceTarget(Editor.TreeView.CustomTreeView<Object> view, TreeViewItem item, Editor.TreeView.PathTreeView.RightRowGUIArgs args, Object customData)
        {
            var rectTmp = args.rowRect;
            rectTmp.width -= GUI_WIDTH_MENU_BUTTON;
            if (null == customData)
                EditorGUI.TextField(rectTmp, item.displayName);
            else
            {
                EditorGUI.BeginDisabledGroup(true);
                {
                    EditorGUI.ObjectField(rectTmp, customData, customData.GetType(), true);
                }
                EditorGUI.EndDisabledGroup();
            }

            rectTmp.x += args.rowRect.width - GUI_WIDTH_MENU_BUTTON;
            rectTmp.width = GUI_WIDTH_MENU_BUTTON;
            if (GUI.Button(rectTmp, "Open"))
            {
                AssetDatabase.OpenAsset(customData);
            }
        }

        private void DrawDeleteUnsusedAssetsButton()
        {
            if (_unusedFilePath.Count > 0)
            {
                var appendString = new System.Text.StringBuilder();
                int maxShowAssetPath = System.Math.Min(_unusedFilePath.Count, 10);
                for (int i = 0; i < maxShowAssetPath; ++i)
                {
                    appendString.Append(_unusedFilePath[i]);
                    appendString.Append('\n');
                }
                if (_unusedFilePath.Count > maxShowAssetPath)
                {
                    appendString.Append("more assets...");
                }

                var titleDisplay = _unusedFilePath.Count > 1 ? string.Format("Delete selected {0} assets", _unusedFilePath.Count) : "Delete selected asset";

                if (EditorUtility.DisplayDialog(string.Format(titleDisplay, _unusedFilePath.Count), appendString.ToString(), "Delete", "Cancel"))
                {
                    DeleteToTrash();
                }
            }
        }

        private void DeleteToTrash()
        {
            var deleteFailedPaths = new List<string>();
            bool result = AssetDatabase.MoveAssetsToTrash(_unusedFilePath.ToArray(), deleteFailedPaths);

            if (deleteFailedPaths.Count > 0)
            {
                foreach (var iter in deleteFailedPaths)
                {
                    Debug.LogError("FindReferenceWindow DeleteToTrash erorr: path=" + iter);
                }
            }

            if (!result)
            {
                Debug.LogError("FindReferenceWindow DeleteToTrash has error");
                return;
            }

            for (int i = _unusedFilePath.Count - 1; i >= 0; --i)
            {
                DeleteEmptyFolder(_unusedFilePath[i], (folderPath) =>
                {
                    System.IO.Directory.Delete(folderPath, true);

                    var metaPath = folderPath + ".meta";
                    if (System.IO.File.Exists(metaPath))
                        System.IO.File.Delete(metaPath);
                    else
                        Debug.LogError("FindReferenceWindow DeleteToTrash error: not found meta path=" + metaPath);
                }, ".DS_Store");
            }

            _unusedFilePath.Clear();
            _treeViewUnuse.Clear();
            AssetDatabase.Refresh();
        }

        private void FindReferencesInProject(params string[] selectAssets)
        {
            _treeViewUnuse.Clear();
            _unusedFilePath.Clear();
            _statusFind = FindStatus.None;
            _iReferenceCountTmp = 0;

            FindReferencesInProjectBase((mapFindDependencies) =>
            {
                foreach (var iter in mapFindDependencies)
                {
                    var listDependenciesTmp = iter.Value.dependencies;
                    var pathAsset = iter.Value.assetPath;

                    if (listDependenciesTmp.Count == 0)
                    {
                        _treeViewUnuse.AddPath(pathAsset);
                        _unusedFilePath.Add(pathAsset);
                    }
                    else
                    {
                        _treeViewReferenced.AddPath(iter.Value.assetPath, listDependenciesTmp);
                        ++_iReferenceCountTmp;
                    }
                }

                _statusFind = mapFindDependencies.Count > 0 ? FindStatus.FindReference : FindStatus.NoReference;
                _treeViewUnuse.ExpandAll();
                _treeViewReferenced.ExpandAll();
                _prevRectDrawFolder.width = _prevRectDrawFolder.height = 0;

                //设置拆分绘制窗口
                if (_treeViewUnuse.hasChildren && _treeViewReferenced.hasChildren)
                {
                    _dragLineSeparator.SetSplitWindow(this, 0.5f, 0.5f);
                }

                if (_unusedFilePath.Count > 0)
                    _treeViewUnuse.AddHeader("Delete Unuse Assets", () => DrawDeleteUnsusedAssetsButton());

                this.Repaint();
            }, true, selectAssets);
        }

        private void OnRefereceViewSingleClickedItemCallBack(string path, List<string> customData)
        {
            _showFoundDepenciesItemsPath.Clear();
            _currentSelectionPathWheSingleClick = _treeViewReferenced.GetSelectionPath();
        }

        private FindInternalDepnciesType GetFindInternalDepnciesType(string rowLabel, Object customData)
        {
            if (customData.GetType() == typeof(UnityEditor.SceneAsset))
                return FindInternalDepnciesType.SceneAsset;
            else if (customData.GetType() == typeof(GameObject) && rowLabel.LastIndexOf(".prefab") >= 0)
                return FindInternalDepnciesType.Prefab;
            else
                return FindInternalDepnciesType.Unknown;
        }

        private void OnDrawFindReferenceTarget(Editor.TreeView.CustomTreeView<Object> view, TreeViewItem item, Editor.TreeView.PathTreeView.RightRowGUIArgs args, Object customData)
        {
            if (null == customData)
                EditorGUI.TextField(args.rowRect, item.displayName);
            else
            {
                var rectTmp = args.rowRect;
                var findInternalDepnciesType = GetFindInternalDepnciesType(args.label, customData);
                var firstParent = view.GetFirstParent(item);
                Dictionary<Object, FindInternalDepnciesInfo> findValue = null;

                //空出右边的find按钮位置
                _showFoundDepenciesItemsPath.TryGetValue(firstParent, out findValue);
                if (findInternalDepnciesType != FindInternalDepnciesType.Unknown || null != findValue)
                    rectTmp.width -= GUI_WIDTH_MENU_BUTTON;

                FindInternalDepnciesInfo findPropertyPath;
                if (null != findValue)
                {
                    if (findValue.TryGetValue(customData, out findPropertyPath))
                    {
                        //空出左边二级菜单位置
                        rectTmp.x += 32;
                        rectTmp.width -= 32;

                        //空出左边显示变量名字位置
                        if (!string.IsNullOrEmpty(findPropertyPath.propertyPath))
                        {
                            var rectText = rectTmp;
                            rectText.width = Mathf.Min(Screen.width / 8, 150);
                            rectTmp.x += rectText.width;
                            rectTmp.width -= rectText.width;
                            GUI.Label(rectText, findPropertyPath.propertyPath);
                        }
                    }
                    else
                    {
                        rectTmp.x += 16;
                        rectTmp.width -= 16;
                    }
                }

                EditorGUI.BeginDisabledGroup(true);
                {
                    EditorGUI.ObjectField(rectTmp, customData, customData.GetType(), true);
                }
                EditorGUI.EndDisabledGroup();

                if (findInternalDepnciesType != FindInternalDepnciesType.Unknown)
                    DrawFindReferenceTargetButton(view, args, firstParent, customData, findInternalDepnciesType);
            }
        }

        private void DrawFindReferenceTargetButton(Editor.TreeView.CustomTreeView<Object> view, Editor.TreeView.PathTreeView.RightRowGUIArgs args, TreeViewItem firstParent, Object customData, FindInternalDepnciesType type)
        {
            //已经find过的节点不再重新find
            if (_showFoundDepenciesItemsPath.ContainsKey(firstParent))
                return;

            var rectTmp = args.rowRect;
            rectTmp.x += args.rowRect.width - GUI_WIDTH_MENU_BUTTON;
            rectTmp.width = GUI_WIDTH_MENU_BUTTON;

            if (!GUI.Button(rectTmp, "Find"))
                return;

            var oldSelection = Selection.objects.Length == 1 ? Selection.objects[0] : null;
            var assetPath = AssetDatabase.GetAssetPath(customData);
            var loadAssets = new Queue<Object>();
            var isFindSucess = false;

            foreach (var iter in _currentSelectionPathWheSingleClick)
            {
                var loadAsset = AssetDatabase.LoadAssetAtPath<Object>(iter);
                if (null != loadAsset)
                    loadAssets.Enqueue(loadAsset);
            }

            switch (type)
            {
                case FindInternalDepnciesType.SceneAsset:
                    {
                        if (UnityEditor.SceneManagement.EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
                        {
                            UnityEditor.SceneManagement.EditorSceneManager.OpenScene(assetPath);
                            isFindSucess = SelectReferenceInTargets(view, firstParent, args.label, GameObject.FindObjectsOfType<Component>(true), loadAssets);
                        }
                        break;
                    }
                case FindInternalDepnciesType.Prefab:
                    {
                        AssetDatabase.OpenAsset(customData);

                        var prefabStage = GetCurrentPrefabState();
                        var allObjectTmp = prefabStage.prefabContentsRoot.GetComponentsInChildren<Component>(true);
                        isFindSucess = SelectReferenceInTargets(view, firstParent, args.label, allObjectTmp, loadAssets);
                        break;
                    }
                default: Debug.LogError("FindReferenceWindow DrawFindReferenceTarget error: unsupport type=" + type); break;
            }

            if (isFindSucess)
            {
                view.UnSelection();
                view.SetFocus();
                view.SetSelection(new List<int>() { args.item.id });
            }

            //恢复之前选中Project中的asset对象
            if (null != oldSelection && (AssetDatabase.IsMainAsset(oldSelection) || AssetDatabase.IsSubAsset(oldSelection)))
            {
                Selection.objects = new Object[] { oldSelection };
            }
        }

#if UNITY_2021_1_OR_NEWER
        static public UnityEditor.SceneManagement.PrefabStage GetCurrentPrefabState()
        {
            return UnityEditor.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
        }
#elif UNITY_2018_1_OR_NEWER
        static public UnityEditor.Experimental.SceneManagement.PrefabStage GetCurrentPrefabState()
        {
            return UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
        }
#else
        [System.Obsolete("EditorHelper+Compatibility GetCurrentPrefabState error: only support unity 2018 or newer !", true)]
        static public object GetCurrentPrefabState()
        {
            Debug.LogError("EditorHelper+Compatibility GetCurrentPrefabState error: only support unity 2018 or newer !");
            return null;
        }
#endif

        static private bool SelectReferenceInTargetWithAnimator(Dictionary<Object, string> depencies, Object target, Dictionary<Object, Object> findTargets)
        {
            //Animator的clip不在NextVisible中，需要特殊处理
            if (target.GetType() != typeof(Animator))
                return false;

            var animatorCheck = target as Animator;
            if (null == animatorCheck || null == animatorCheck.runtimeAnimatorController || null == animatorCheck.runtimeAnimatorController.animationClips)
                return false;

            var clipInfo = animatorCheck.runtimeAnimatorController.animationClips;
            foreach (var clip in clipInfo)
            {
                if (findTargets.ContainsKey(clip))
                {
                    if (!depencies.ContainsKey(target))
                    {
                        depencies.Add(target, null);
                        continue;
                    }
                    return true;
                }
            }
            return false;
        }

        static private bool SelectReferenceInTargetWithPrefab(Dictionary<Object, string> depencies, Object target, Dictionary<Object, Object> findTargets)
        {
            var componentCheck = target as Component;
            if (null != componentCheck && PrefabUtility.IsAnyPrefabInstanceRoot(componentCheck.gameObject))
            {
                foreach (var iter in findTargets)
                {
                    var gameObjectCheck = iter.Key as GameObject;
                    if (null == gameObjectCheck)
                        continue;

                    var assetPath1 = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(componentCheck.gameObject);
                    var assetPath2 = AssetDatabase.GetAssetPath(gameObjectCheck);
                    if (!string.IsNullOrEmpty(assetPath1) && assetPath1 == assetPath2)
                    {
                        if (!depencies.ContainsKey(componentCheck.gameObject))
                        {
                            depencies.Add(componentCheck.gameObject, null);
                            return true;
                        }
                    }
                }
            }
            return false;
        }

        static private bool SelectReferenceInTargetWithScript(Dictionary<Object, string> depencies, Object target, Dictionary<Object, Object> findTargets, SerializedProperty property, Object parent)
        {
            var componentCheck = target as Component;
            if (null == componentCheck)
                return false;

            var pathCheck = AssetDatabase.GetAssetPath(target);
            if (string.IsNullOrEmpty(pathCheck))
                return false;

            foreach (var iter in findTargets)
            {
                if (pathCheck == AssetDatabase.GetAssetPath(iter.Key))
                {
                    if (!depencies.ContainsKey(parent))
                        depencies.Add(parent, property.propertyPath);
                    return true;
                }
            }

            return false;
        }

        static private bool SelectReferenceInTargetWithMaterial(Dictionary<Object, string> depencies, Object target, Dictionary<Object, Object> findTargets, SerializedProperty property, Object parent)
        {
            if (target.GetType() != typeof(Material))
                return false;

            var materialCheck = target as Material;
            if (materialCheck.HasProperty("_MainTex"))
            {
                if (null != materialCheck.mainTexture && findTargets.ContainsKey(materialCheck.mainTexture))
                {
                    if (!depencies.ContainsKey(parent))
                        depencies.Add(parent, property.propertyPath);
                    return true;
                }
            }
            return false;
        }

        static private void SelectReferenceInTargetRecursive(Dictionary<Object, string> depencies, Dictionary<Object, Object> duplicateTargets, Object[] targets, Dictionary<Object, Object> findTargets, Object parent = null)
        {
            foreach (var iter in targets)
            {
                if (null != iter && !duplicateTargets.ContainsKey(iter))
                    duplicateTargets.Add(iter, null);
                else
                    continue;

                //作为Animator引用
                if (SelectReferenceInTargetWithAnimator(depencies, iter, findTargets))
                    continue;

                //作为prefab引用
                if (SelectReferenceInTargetWithPrefab(depencies, iter, findTargets))
                    continue;

                var serializedObject = new SerializedObject(iter);
                SerializedProperty iterator = serializedObject.GetIterator();

                while (iterator.NextVisible(true))
                {
                    if (iterator.propertyType != SerializedPropertyType.ObjectReference)
                        continue;

                    var objectValueTmp = iterator.objectReferenceValue;
                    if (null == objectValueTmp)
                        continue;

                    //prefab作为脚本引用
                    if (SelectReferenceInTargetWithScript(depencies, objectValueTmp, findTargets, iterator, iter))
                        continue;

                    //材质引用的图片资源在shader下，需要特殊处理
                    if (SelectReferenceInTargetWithMaterial(depencies, objectValueTmp, findTargets, iterator, iter))
                        continue;

                    //其他引用处理
                    bool isEqual = false;
                    if (findTargets.ContainsKey(objectValueTmp))
                        isEqual = true;
                    // else if (null != spriteFind && objectValueTmp == spriteFind)
                    //     isEqual = true;

                    bool isAssetIter = AssetDatabase.IsMainAsset(iter) || AssetDatabase.IsSubAsset(iter);
                    if (isEqual)
                    {
                        if (null != parent && isAssetIter)
                        {
                            if (!depencies.ContainsKey(parent))
                                depencies.Add(parent, iterator.propertyPath);
                        }
                        else
                        {
                            if (!depencies.ContainsKey(iter))
                                depencies.Add(iter, iterator.propertyPath);
                        }
                    }
                    else
                    {
                        //递归查找引用
                        Object[] childComponents = null;
                        if (objectValueTmp is GameObject)
                            childComponents = ((GameObject)objectValueTmp).GetComponentsInChildren<Component>(true);
                        else if (objectValueTmp is MonoBehaviour)
                            childComponents = ((MonoBehaviour)objectValueTmp).GetComponentsInChildren<Component>(true);
                        else
                            childComponents = new Object[] { objectValueTmp };
                        SelectReferenceInTargetRecursive(depencies, duplicateTargets, childComponents, findTargets, isAssetIter && null != parent ? parent : iter);
                    }
                }
            }
        }

        private bool SelectReferenceInTargets(Editor.TreeView.CustomTreeView<Object> view, TreeViewItem firstParent, string fileName, Component[] targets, IEnumerable<Object> loadAssets)
        {
            var depencies = new Dictionary<Object, string>();
            var duplicateTargets = new Dictionary<Object, Object>();
            var findTargets = new Dictionary<Object, Object>();

            foreach (var iter in loadAssets)
            {
                var find = iter;
                if (AssetDatabase.IsMainAsset(find))
                {
                    var subAsset = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(find));
                    foreach (var iter2 in subAsset)
                        findTargets.Add(iter2, null);
                }
                else
                    findTargets.Add(find, null);
            }

            SelectReferenceInTargetRecursive(depencies, duplicateTargets, targets, findTargets);

            //删除之前旧的节点引用路径
            if (_showFoundDepenciesItemsPath.ContainsKey(firstParent))
            {
                Debug.LogWarning("FindReferenceWindow SelectReferenceInTargets warning: duplicate 'Find' will force refresh result");
            }

            //暂时仅能展示1个结果，因为prefab和scene只能打开1个对象去查看引用
            //如果打开新的prefab，则旧的会被顶掉，以后再考虑是否需要Instantiate所有prefab到场景中，然后去查看引用？
            foreach (var iter in _showFoundDepenciesItemsPath)
            {
                foreach (var iter2 in iter.Value)
                    view.RemovePath(iter2.Value.path);
            }
            _showFoundDepenciesItemsPath.Clear();

            //为了避免重名对象导致AddPath时候只能显示1个节点，需要重命名处理
            var duplicateObjectName = new Dictionary<string, int>();

            if (null != depencies && depencies.Count > 0)
            {
                //添加子节点给右边窗口，让它绘制出来
                var tempObjectPath = new Dictionary<Object, FindInternalDepnciesInfo>();
                foreach (var iter in depencies)
                {
                    var pathTmp = fileName + "/" + iter.Key.name;

                    //当发现有重名对象时候进行重命名
                    if (duplicateObjectName.ContainsKey(pathTmp))
                    {
                        var oldPathName = pathTmp;
                        var duplicateNameCount = duplicateObjectName[oldPathName];
                        pathTmp += "(" + duplicateNameCount.ToString() + ")";
                        duplicateObjectName[oldPathName] = duplicateNameCount + 1;
                    }
                    else
                        duplicateObjectName.Add(pathTmp, 1);

                    tempObjectPath.Add(iter.Key, new FindInternalDepnciesInfo() { path = pathTmp, propertyPath = iter.Value });
                    view.AddPath(pathTmp, iter.Key);
                }

                _showFoundDepenciesItemsPath.Add(firstParent, tempObjectPath);
                view.SetExpanded(firstParent.id, true);
                return true;
            }
            else
                return false;
        }

        static private void FindReferencesInProjectBase(System.Action<Dictionary<string, DependentInfo>> callbackEnd, bool useProgressBar, params string[] selectAssets)
        {
            var cachedDepenciesInfo = GetDepenciesFromCached();

            //some prefab only can changed when save scene
            if (!Application.isPlaying)
            {
                if (!string.IsNullOrEmpty(UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().name))
                {
#if UNITY_5_3_OR_NEWER
                    UnityEditor.SceneManagement.EditorSceneManager.SaveOpenScenes();
#else
                    EditorApplication.SaveScene();
#endif
                }
            }

            if (selectAssets.Length > 0)
            {
                GetDependenciesInProject(selectAssets, cachedDepenciesInfo, (Dictionary<string, DependentInfo> mapFindDependencies) =>
                {
                    if (null != callbackEnd)
                    {
                        callbackEnd(mapFindDependencies);
                        return;
                    }
                }, useProgressBar);
            }
        }

        static private Dictionary<string, CacheDepenciesInfo> GetDepenciesFromCached()
        {
            Dictionary<string, CacheDepenciesInfo> retValue = null;
            var savePath = System.IO.Path.Combine(Application.dataPath, "../Library/shacoDepenciesCache.txt");

            if (!System.IO.File.Exists(savePath))
                return retValue;

            retValue = new Dictionary<string, CacheDepenciesInfo>();
            var dependenciesGUIDTmp = new List<string>();
            try
            {
                using (var readStream = new System.IO.StreamReader(savePath))
                {
                    while (true)
                    {
                        var assetPath = readStream.ReadLine();
                        if (string.IsNullOrEmpty(assetPath))
                            break;

                        var hash128String = readStream.ReadLine();
                        if (string.IsNullOrEmpty(hash128String))
                        {
                            Debug.LogError("FindReferenceWindow GetDepenciesFromCached error: not found has128 string, name=" + assetPath);
                            break;
                        }

                        var newDependInfo = new CacheDepenciesInfo(assetPath, Hash128.Parse(hash128String));

                        var dependCountString = readStream.ReadLine();
                        if (string.IsNullOrEmpty(assetPath))
                        {
                            Debug.LogError("FindReferenceWindow GetDepenciesFromCached error: not found depend count, name=" + assetPath);
                            break;
                        }

                        if (!int.TryParse(dependCountString, out var dependCount))
                        {
                            Debug.LogError("FindReferenceWindow GetDepenciesFromCached error: can't parse dependCountString=" + dependCountString);
                            continue;
                        }
                        dependenciesGUIDTmp.Clear();
                        for (int i = 0; i < dependCount; ++i)
                        {
                            var dependAssetGUID = readStream.ReadLine();
                            if (string.IsNullOrEmpty(dependAssetGUID))
                            {
                                Debug.LogError("FindReferenceWindow GetDepenciesFromCached error: not found depend asset, name=" + assetPath);
                                break;
                            }
                            dependenciesGUIDTmp.Add(dependAssetGUID);
                        }
                        newDependInfo.dependenciesGUID = dependenciesGUIDTmp.ToArray();

                        if (retValue.ContainsKey(assetPath))
                            Debug.LogError("FindReferenceWindow GetDepenciesFromCached error: not found depend count, name=" + assetPath);
                        else
                            retValue.Add(assetPath, newDependInfo);
                    }
                    readStream.Close();
                }
            }
            catch (System.Exception e)
            {
                Debug.LogError("FindReferenceWindow GetDepenciesFromCached exception: e=" + e);
                System.IO.File.Delete(savePath);
            }
            return retValue;
        }

        static private void SaveDependenciesToCahce(Dictionary<string, DepenciesInfo[]> dependenciesInfo)
        {
            if (null == dependenciesInfo || 0 == dependenciesInfo.Count)
                return;

            var savePath = System.IO.Path.Combine(Application.dataPath, "../Library/shacoDepenciesCache.txt");
            List<string> dependenciesTmp = new List<string>();
            using (var writeStream = new System.IO.StreamWriter(savePath))
            {
                foreach (var iter in dependenciesInfo)
                {
                    var hash128String = AssetDatabase.GetAssetDependencyHash(iter.Key);

                    writeStream.WriteLine(iter.Key);
                    writeStream.WriteLine(hash128String);

                    dependenciesTmp.Clear();
                    for (int i = iter.Value.Length - 1; i >= 0; --i)
                    {
                        //过滤自己
                        if (iter.Value[i].assetPath == iter.Key)
                            continue;
                        dependenciesTmp.Add(iter.Value[i].assetPath);
                    }

                    writeStream.WriteLine(dependenciesTmp.Count);

                    for (int i = dependenciesTmp.Count - 1; i >= 0; --i)
                    {
                        writeStream.WriteLine(AssetDatabase.AssetPathToGUID(dependenciesTmp[i]));
                    }
                }
                writeStream.Close();

                Debug.Log("FindReferenceWindow SaveDependenciesToCahce: write depencies, path=" + savePath);
            }
        }

        static private void GetDependenciesInProject(string[] selectAssetsPath, Dictionary<string, CacheDepenciesInfo> cachedDepenciesInfo, System.Action<Dictionary<string, DependentInfo>> callback, bool useProgressBar)
        {
            var ret = new Dictionary<string, DependentInfo>();
            bool shouldCancel = false;

            if (useProgressBar)
                shouldCancel = EditorUtility.DisplayCancelableProgressBar("get all files...", "please wait", 0);

            var allSearchAssets = AssetDatabase.FindAssets("t:spriteatlas t:prefab t:scene t:ScriptableObject t:Material t:AnimatorController t:Animation");
            for (int i = 0; i < allSearchAssets.Length; ++i)
            {
                allSearchAssets[i] = AssetDatabase.GUIDToAssetPath(allSearchAssets[i]);
            }

            //collection all files dependencies
            var allDependencies = new Dictionary<string, DepenciesInfo[]>();
            var currentCollectionDepenciesPath = string.Empty;
            int currentCollectionIndex = 0;
            bool isDepenciesCacheChanged = false;
            ForeachCoroutine(allSearchAssets, (object data) =>
            {
                try
                {
                    currentCollectionDepenciesPath = data as string;
                    isDepenciesCacheChanged |= CollectionDepencies(cachedDepenciesInfo, allDependencies, currentCollectionDepenciesPath);
                    ++currentCollectionIndex;
                }
                catch (System.Exception e)
                {
                    Debug.LogError("FindReferenceWindow Collection depencies exception: path=" + currentCollectionDepenciesPath + "\ne=" + e);
                }
                return !shouldCancel;
            }, (float percent) =>
            {
                if (useProgressBar)
                    shouldCancel = EditorUtility.DisplayCancelableProgressBar(string.Format("collection reference [{0}/{1}]", currentCollectionIndex, allSearchAssets.Length), currentCollectionDepenciesPath, 0.5f * percent);
                if (shouldCancel)
                {
                    EditorUtility.ClearProgressBar();
                }

                if (percent >= 1 && !shouldCancel)
                {
                    //select dependencies
                    var currentSelectDepenciesAssetPath = string.Empty;
                    var currentSelectDepenciesIndex = 0;
                    ForeachCoroutine(selectAssetsPath, (object data) =>
                    {
                        bool retValue = true;
                        try
                        {
                            currentSelectDepenciesAssetPath = data as string;
                            retValue = !shouldCancel && SelectDepencies(ret, allSearchAssets, allDependencies, currentSelectDepenciesAssetPath);
                            ++currentSelectDepenciesIndex;
                        }
                        catch (System.Exception e)
                        {
                            Debug.LogError("FindReferenceWindow Select dependencies exception: path=" + currentSelectDepenciesAssetPath + "\ne=" + e);
                        }
                        return retValue;
                    }, (float percent2) =>
                    {
                        try
                        {
                            if (useProgressBar)
                                shouldCancel = EditorUtility.DisplayCancelableProgressBar(string.Format("select depencies [{0}/{1}]", currentSelectDepenciesIndex, selectAssetsPath.Length), currentSelectDepenciesAssetPath, 0.5f * percent2 + 0.5f);
                            if (percent2 >= 1)
                            {
                                EditorUtility.ClearProgressBar();

                                //保存引用关系表到本地缓存文件中
                                if (isDepenciesCacheChanged)
                                {
                                    SaveDependenciesToCahce(allDependencies);
                                }
                                callback(ret);
                            }
                        }
                        catch (System.Exception e)
                        {
                            Debug.LogError("FindReferenceWindow GetDependenciesInProject exception: " + e);
                            EditorUtility.ClearProgressBar();
                        }
                    }, 0.1f);
                }
            }, 0.1f);
        }

        static private void ForeachCoroutine(IList collections, System.Func<object, bool> callbackData, System.Action<float> callbackProgress = null, float speed = 0.05f)
        {
            if (speed < 0)
                speed = 0.05f;
            if (speed > 1)
                speed = 1;

            EditorApplication.update -= Update;
            EditorApplication.update += Update;

            _currentForeachIndex = 0;
            _currentForeachCollections = collections;
            _currentForeachCallbackData = callbackData;
            _currentForeachcallbackProgress = callbackProgress;
            _currentForeachCountInPerFrame = (int)(_currentForeachCollections.Count * speed);
            if (_currentForeachCountInPerFrame <= 0)
                _currentForeachCountInPerFrame = 1;
        }

        static private bool CollectionDepencies(Dictionary<string, CacheDepenciesInfo> cachedDepenciesInfo, Dictionary<string, DepenciesInfo[]> allDependencies, string path)
        {
            if (allDependencies.ContainsKey(path))
                return false;

            //优先从缓存引用获取信息
            if (null != cachedDepenciesInfo)
            {
                CacheDepenciesInfo findInfo = null;
                if (cachedDepenciesInfo.TryGetValue(path, out findInfo))
                {
                    //如果二者的hash引用值一样则表示引用没有发生变化，直接用缓存的引用信息不再重新动态获取了
                    var hash128String = AssetDatabase.GetAssetDependencyHash(path);
                    if (hash128String.Equals(findInfo.hash128))
                    {
                        allDependencies.Add(path, AssetsGUIDToDepenciesInfo(findInfo.dependenciesGUID));
                        return false;
                    }
                }
            }

#if UNITY_5_3_OR_NEWER
            var listDependence = AssetDatabase.GetDependencies(path);
#else
            var listDependence = AssetDatabase.GetDependencies(new string[]{ path });
#endif
            allDependencies.Add(path, AssetsPathToDepenciesInfo(listDependence));
            return true;
        }

        static private DepenciesInfo[] AssetsGUIDToDepenciesInfo(string[] assetsGUID)
        {
            var retValue = new DepenciesInfo[assetsGUID.Length];
            for (int i = assetsGUID.Length - 1; i >= 0; --i)
            {
                var newInfo = new DepenciesInfo();
                retValue[i] = newInfo;
                newInfo.assetPath = AssetDatabase.GUIDToAssetPath(assetsGUID[i]);
                newInfo.guid = assetsGUID[i];
            }
            return retValue;
        }

        static private DepenciesInfo[] AssetsPathToDepenciesInfo(string[] assetsPath)
        {
            var retValue = new DepenciesInfo[assetsPath.Length];
            for (int i = assetsPath.Length - 1; i >= 0; --i)
            {
                var newInfo = new DepenciesInfo();
                retValue[i] = newInfo;
                newInfo.assetPath = assetsPath[i];
                newInfo.guid = AssetDatabase.AssetPathToGUID(assetsPath[i]);
            }
            return retValue;
        }

        static private bool SelectDepencies(Dictionary<string, DependentInfo> selectDepencies, string[] allSearchAssets, Dictionary<string, DepenciesInfo[]> allDependencies, string assetPath)
        {
            var selectPath = assetPath;
            if (System.IO.Directory.Exists(selectPath))
                return true;

            var selectGUID = AssetDatabase.AssetPathToGUID(selectPath);

            if (!string.IsNullOrEmpty(selectPath) && !selectDepencies.ContainsKey(assetPath))
            {
                selectDepencies.Add(assetPath, new DependentInfo(assetPath));
            }

            DependentInfo selectDependenceInfo = selectDepencies[assetPath];
            for (int i = allSearchAssets.Length - 1; i >= 0; --i)
            {
                var pathTmp = allSearchAssets[i];
                DepenciesInfo[] listDependence = null;
                if (allDependencies.TryGetValue(pathTmp, out listDependence))
                {
                    for (int j = 0; j < listDependence.Length; ++j)
                    {
                        if (listDependence[j].guid == selectGUID)
                        {
                            selectDependenceInfo.dependencies.Add(pathTmp);
                            break;
                        }
                    }
                }
            }

            if (!selectDepencies.ContainsKey(assetPath))
            {
                Debug.LogError("not find asset=" + assetPath + " in dictionary");
                return false;
            }

            //remove self dependence
            var listDependenciesTmp = selectDependenceInfo.dependencies;
            for (int i = listDependenciesTmp.Count - 1; i >= 0; --i)
            {
                if (listDependenciesTmp[i] == assetPath)
                {
                    listDependenciesTmp.RemoveAt(i);
                }
            }
            return true;
        }

        /// <summary>
        /// 递归删除空文件
        /// <param name="path">递归开始的路径，从该文件夹开始往上层依次删除空文件夹</param>
        /// <param name="callbackDelete">自定义删除文件方法，如果为空默认使用System.IO.Directory.Delete</param>
        /// <param name="ignorePatterns">判断文件数量的时候需要过滤的文件后缀名，如果文件数量为0则会删除该文件夹</param>
        /// </summary>
        static public bool DeleteEmptyFolder(string path, System.Action<string> callbackDelete, params string[] ignorePatterns)
        {
            var retValue = false;

            //如果文件夹内容空，则删除它
            var folderPath = string.Empty;

            //本身是文件夹
            if (System.IO.Directory.Exists(path))
            {
                folderPath = path;
            }
            //可能是文件路径
            else
            {
                var parent = System.IO.Directory.GetParent(path);
                if (null == parent)
                    return false;

                folderPath = parent.FullName;

                if (!System.IO.Directory.Exists(folderPath))
                    return false;
            }

            //获取文件夹下所有文件
            bool isEmptyFolder = IsEmptyFolder(folderPath, ignorePatterns);

            //文件内容为空
            if (isEmptyFolder)
            {
                if (null != callbackDelete)
                    callbackDelete(folderPath);
                else
                {
                    try
                    {
                        System.IO.Directory.Delete(folderPath, true);
                    }
                    catch (System.Exception e)
                    {
                        Debug.LogError("FindReferenceWindow DeleteEmptyFolder exception: path=" + folderPath + " e=" + e);
                        return false;
                    }
                }
                retValue = true;

                //获取上级目录递归确认空文件夹并删除
                folderPath = System.IO.Directory.GetParent(folderPath).FullName;
                DeleteEmptyFolder(folderPath, callbackDelete, ignorePatterns);
            }
            return retValue;
        }

        /// <summary>
        /// 判断文件夹是否为空
        /// <param name="path">文件夹路径</param>
        /// <param name="ignorePatterns">判断文件数量的时候需要过滤的文件后缀名，如果文件数量为0则会删除该文件夹</param>
        /// </summary>
        static public bool IsEmptyFolder(string path, params string[] ignorePatterns)
        {
            if (!System.IO.Directory.Exists(path))
                return false;

            var allFilesPath = System.IO.Directory.GetFiles(path, "*", System.IO.SearchOption.TopDirectoryOnly);
            bool retValue = true;
            if (null != allFilesPath && allFilesPath.Length > 0)
            {
                for (int i = allFilesPath.Length - 1; i >= 0; --i)
                {
                    var assetPath = allFilesPath[i];

                    if (null == ignorePatterns || ignorePatterns.Length == 0)
                    {
                        retValue = false;
                        break;
                    }
                    else
                    {
                        bool hasIngorePattern = false;
                        for (int j = ignorePatterns.Length - 1; j >= 0; --j)
                        {
                            if (assetPath.EndsWith(ignorePatterns[j]))
                            {
                                hasIngorePattern = true;
                                break;
                            }
                        }
                        retValue = hasIngorePattern;
                    }

                    if (!retValue)
                        break;
                }
            }

            if (retValue)
            {
                var allFoldersPath = System.IO.Directory.GetDirectories(path, "*", System.IO.SearchOption.TopDirectoryOnly);
                if (null != allFoldersPath && allFoldersPath.Length > 0)
                {
                    for (int i = 0; i < allFoldersPath.Length; ++i)
                    {
                        retValue = IsEmptyFolder(allFoldersPath[i], ignorePatterns);
                        if (!retValue)
                            break;
                    }
                }
            }
            return retValue;
        }

        /// <summary>
        /// 筛选是否被引用到的对象，返回被引用到的目标以及资源路径
        /// <param name="targets">可能引用到的所有目标</param>
        /// <param name="findTargets">需要查找被引用的目标</param>
        /// </summary>
        static public Dictionary<Object, string> SelectReferenceInTarget(Object[] targets, Object findTarget)
        {
            var retValue = new Dictionary<Object, string>();
            var duplicateTargets = new Dictionary<Object, Object>();
            SelectReferenceInTargetRecursive(retValue, duplicateTargets, targets, new Dictionary<Object, Object>() { { findTarget, findTarget } });
            return retValue;
        }
        static public Dictionary<Object, string> SelectReferenceInTarget(Object[] targets, Dictionary<Object, Object> findTargets)
        {
            var retValue = new Dictionary<Object, string>();
            var duplicateTargets = new Dictionary<Object, Object>();
            SelectReferenceInTargetRecursive(retValue, duplicateTargets, targets, findTargets);
            return retValue;
        }
    }
}